package lapsnake.model.expr;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;

import lapsnake.model.*;
import lapsnake.model.bounceback.StringNode;
import lapsnake.model.data.ChildField;
import lapsnake.model.data.Property;
import lapsnake.model.data.TypedListener;
import lapsnake.model.data.VetoableProperty;
import lapsnake.model.dnd.ActionNodePanel;
import lapsnake.py.ast.Value;
import lapsnake.ui.JIdentifierField;

import org.python.antlr.ast.*;

import owg.util.Calc;

/**Wrapper class with helper functions for Name nodes*/
public class LapName extends LapExpression implements CharSequence, Value<String>, VetoableChangeListener {
	
	public VetoableProperty<String> identifier = new VetoableProperty<>(this, String.class);
	
	public LapName(PyAST pyAST, ChildField<? super LapName> parentField, Name node) throws PropertyVetoException {
		super(pyAST, parentField, node);
		
		this.identifier.setInternal(node.getInternalId());
		validate(identifier.get());
		
		int nodeLength = node.getCharStopIndex()-node.getCharStartIndex();
		if(nodeLength > identifier.get().length())
		{
			//There's a bug in the compiler where it allocates way too many characters to the Name node. Needs complex fix...
			String token = pyAST.getScript().substring(node.getCharStartIndex(), node.getCharStopIndex());
			int idPosition = token.indexOf(identifier.get());
			String prefix = token.substring(0, idPosition);
			String suffix = token.substring(idPosition + identifier.get().length());
			int numPreParens = Calc.countOccurrences(prefix, '(');
			int numSufParens = 0;
			int suffixPos = 0;
			while(numSufParens < numPreParens)
			{
				char c = suffix.charAt(suffixPos);
				if(c == ')')
					numSufParens ++;
				suffixPos ++;
			}
			int endPos = idPosition + identifier.get().length() + suffixPos;
			if(nodeLength > endPos)
			{
				pyAST.warning(getClass().getSimpleName()+" node("+node+"="+identifier.get()+") at line "+
					node.getLine()+", pos. "+node.getCharPositionInLine()+" has invalid character end location: "
					+ "Length is "+nodeLength+" but should be "+endPos+".");
				node.setCharStopIndex(node.getCharStartIndex()+endPos);
			}
		}
		
		buildTextNodes(node);
	}
	
	public LapName(PyAST pyAST, ChildField<? super LapName> parentField, String name) throws PropertyVetoException
	{
		super(pyAST, parentField, null);
		this.identifier.setInternal(name);
		validate(identifier.get());
		setTextNodes(new StringNode(name));
	}

	@Override
	public char charAt(int arg0) 
	{
		return identifier.get().charAt(arg0);
	}
	
	@Override
	public int length() 
	{
		return identifier.get().length();
	}
	
	@Override
	public CharSequence subSequence(int arg0, int arg1) 
	{
		return identifier.get().subSequence(arg0, arg1);
	}
	
	/**
	 * Validate an identifier.
	 * 
	 *<pre> identifier ::=  (letter|"_") (letter | digit | "_")*
	 * letter     ::=  lowercase | uppercase
	 * lowercase  ::=  "a"..."z"
	 * uppercase  ::=  "A"..."Z"
	 * digit      ::=  "0"..."9"</pre>
	 * 
	 * @param name The identifier name.
	 * @throws IllegalArgumentException If the name is not a valid identifier.
	 */
	public static void validate(String name) throws IllegalArgumentException
	{
		if(name == null)
			throw new IllegalArgumentException(null+" is not a valid identifier.");
		if(name.isEmpty())
			throw new IllegalArgumentException(name+" is not valid because it is empty.");
		
		char c = name.charAt(0);
		if(!isLowercase(c) && !isUppercase(c) && c != '_')
			throw new IllegalArgumentException(name+" cannot start with the character '"+name.charAt(0)+"'.");
		for(int i = 1; i<name.length(); i++)
		{
			c = name.charAt(i);
			if(!isLowercase(c) && !isUppercase(c) && !isDigit(c) && c != '_')
				throw new IllegalArgumentException(name+" cannot contain the illegal character '"+name.charAt(i)+"'.");
		}
		
	}
	
	public static boolean isLowercase(char c)
	{
		return c >= 'a' && c <= 'z';
	}
	public static boolean isUppercase(char c)
	{
		return c >= 'A' && c <= 'Z';
	}
	public static boolean isDigit(char c)
	{
		return c >= '0' && c <= '9';
	}
	@Override
	public String value()
	{
		return identifier.get();
	}
	
	@Override
	protected void populateCompactUI(ActionNodePanel<LapNode> ui)
	{
		JIdentifierField editor = new JIdentifierField(identifier.get());
		editor.addActionListener(new ActionListener()
		{
			//Note: need lis reference to live as long as the editor does, or the weakreference might be destroyed prematurely
			TypedListener<String> lis;
			{
				lis = new TypedListener<String>(){
					@Override
					public void valueChanged(
						Property<? extends String> property, String oldValue)
					{
						//Update editor to reflect new value
						editor.setText(property.get());
					}
					
				};
				identifier.addListener(lis);
			}
			@Override
			public void actionPerformed(ActionEvent e)
			{
				try
				{
					//Update value from editor
					//Exclude lis to prevent unnecessary reacharound.
					identifier.change(editor.getText(), lis, true);
				}
				catch (PropertyVetoException err)
				{
					//Reset value if it failed
					ui.rootPanel.userError(editor, err);
					editor.setText(identifier.get());
				}
			}
		});
		ui.add(editor);
	}

	@Override
	public void vetoableChange(PropertyChangeEvent evt)
		throws PropertyVetoException
	{
		if(!isRegistered())
			return;
		assert evt.getSource() == identifier;
		String newValue = (String) evt.getNewValue();
		try {
			validate(newValue);
		} catch (IllegalArgumentException e) {
			throw new PropertyVetoException(e.getMessage(), evt);
		}
		changeNodeText(newValue);
	}
}
